﻿using System;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
using Windows.UI.Core;

namespace Coding4Fun.Maelstrom.Communication
{
	public sealed class SocketClient
	{
		public event EventHandler<PacketReceivedEventArgs> PacketReceived;

		public IGamePacket LastPacket { get; set; }

		private readonly StreamSocket _client;
		private DataReader _reader;
		private DataWriter _writer;
		private CoreDispatcher _dispatcher;

		public SocketClient()
		{
			_client = new StreamSocket();
			CoreWindow cw = CoreWindow.GetForCurrentThread();
			if(cw != null)
				_dispatcher = cw.Dispatcher;
		}

		public IAsyncAction ConnectAsync(string address, int port)
		{
			return ConnectAsyncInternal(address, port).AsAsyncAction();
		}

		private async Task ConnectAsyncInternal(string address, int port)
		{
			await _client.ConnectAsync(new HostName(address), port.ToString());
			_reader = new DataReader(_client.InputStream);
			_writer = new DataWriter(_client.OutputStream);

			// variable assignment just to remove compiler warning since we're not awaiting
			Task t = Task.Run(async () =>
			{
				while(true)
				{
					await _reader.LoadAsync(sizeof(Int32));
					PacketType type = (PacketType)_reader.ReadInt32();
					LastPacket = await ReadPacketAsync(type);
					if(PacketReceived != null)
					{
						if(_dispatcher == null || _dispatcher.HasThreadAccess)
							PacketReceived(this, new PacketReceivedEventArgs { Packet = LastPacket });
						else
							_dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => PacketReceived(this, new PacketReceivedEventArgs { Packet = LastPacket }));
					}
				}
			});
		}

		public IAsyncOperation<IGamePacket> ReadPacketAsync()
		{
			return ReadPacketAsyncInternal().AsAsyncOperation();
		}

		private async Task<IGamePacket> ReadPacketAsyncInternal()
		{
			await _reader.LoadAsync(sizeof(Int32));
			PacketType type = (PacketType)_reader.ReadInt32();
			return await ReadPacketAsync(type);
		}

		private async Task<IGamePacket> ReadPacketAsync(PacketType type)
		{
			switch(type)
			{
				case PacketType.UserInputPacket:
					UserInputPacket uip = new UserInputPacket();
					await uip.FromDataReaderAsync(_reader);
					return uip;

				case PacketType.GameStatePacket:
					GameStatePacket gsp = new GameStatePacket();
					await gsp.FromDataReaderAsync(_reader);
					return gsp;

				default:
					throw new Exception("Unknown packet type received: " + type);
			}
		}

		public IAsyncAction WritePacketAsync(IGamePacket packet)
		{
			return WritePacketAsyncInternal(packet).AsAsyncAction();
		}

		private async Task WritePacketAsyncInternal(IGamePacket packet)
		{
			if(_writer != null)
			{
				_writer.WriteInt32((int) packet.Type);
				packet.WritePacket(_writer);
				await _writer.StoreAsync();
			}
		}
	}
}
